﻿/* 2012/5/1 */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
using System.IO;

namespace Cosmos.Windows.Forms.FileList
{
    /// <summary>
    /// Represents a Windows list box control, which displays a collection of file names.
    /// </summary>
    [DefaultEvent("SelectedChanged")]
    public partial class FileNameListBox : UserControl, IFileList
    {
        /// <summary>
        /// Represents an item in FileNameListBox control.
        /// </summary>
        class FileNameListItem : IFileListItem
        {
            /// <summary>
            /// The path represented by current item.
            /// </summary>
            private string _filePath;
            private string _fileName;

            /// <summary>
            /// Initializes a new instance of the FileNameListItem with specified path.
            /// </summary>
            /// <param name="imagePath"></param>
            public FileNameListItem(string filePath)
            {
                FilePath = filePath;
            }

            /// <summary>
            /// Returns the file name represented by current item.
            /// </summary>
            /// <returns>The file name represented by current item.</returns>
            public override string ToString()
            {
                return FileName;
            }

            /// <summary>
            /// Gets the file name represented by current item.
            /// </summary>
            public string FileName
            {
                get
                {
                    return _fileName;
                }
            }

            /// <summary>
            /// Gets or sets file path represented by current item.
            /// </summary>
            public string FilePath
            {
                get
                {
                    return _filePath;
                }

                set
                {
                    _filePath = value;
                    _fileName = Path.GetFileName(value);
                }
            }
        }

        /// <summary>
        /// The hash set which contains added paths.
        /// </summary>
        private HashSet<string> _pathSet;

        /// <summary>
        /// Initializes a new instance of FileNameListBox class.
        /// </summary>   
        public FileNameListBox()
        {
            InitializeComponent();

            listBox.SelectionMode = SelectionMode.MultiExtended;

            IsRenamingFile = false;
            IsSelectingListBox = false;

            _pathSet = new HashSet<string>();
        }

        #region FileList Methods

        public void SelectPrevious()
        {
            IsSelectingListBox = true;
            if (listBox.SelectedItem == null)
            {
                int itemCount = Count;
                if (itemCount == 0)
                    return;

                listBox.SelectedIndex = itemCount - 1;
            }
            else
            {
                int fileIndex = listBox.SelectedIndex;
                int itemCount = Count;
                listBox.SetSelected(fileIndex, false);
                fileIndex = (--fileIndex + itemCount) % itemCount;
                listBox.SetSelected(fileIndex, true);
            }
            IsSelectingListBox = false;

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SelectNext()
        {
            IsSelectingListBox = true;
            if (listBox.SelectedItem == null)
            {
                int itemCount = Count;
                if (itemCount == 0)
                    return;

                listBox.SelectedIndex = 0;
            }
            else
            {
                int fileIndex = listBox.SelectedIndex;
                int itemCount = Count;
                listBox.SetSelected(fileIndex, false);
                fileIndex = (++fileIndex) % itemCount;
                listBox.SetSelected(fileIndex, true);
            }
            IsSelectingListBox = false;

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SelectNone()
        {
            IsSelectingListBox = true;
            int itemCount = Count;
            listBox.BeginUpdate();
            for (int i = 0; i < itemCount; i++)
            {
                listBox.SetSelected(i, false);
            }
            listBox.EndUpdate();
            IsSelectingListBox = false;

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SelectAll()
        {
            IsSelectingListBox = true;
            int itemCount = Count;
            listBox.BeginUpdate();
            for (int i = 0; i < itemCount; i++)
            {
                listBox.SetSelected(i, true);
            }
            listBox.EndUpdate();
            IsSelectingListBox = false;

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SelectInvert()
        {
            IsSelectingListBox = true;
            int itemCount = Count;
            listBox.BeginUpdate();
            for (int i = 0; i < itemCount; i++)
            {
                if (listBox.GetSelected(i))
                {
                    listBox.SetSelected(i, false);
                }
                else
                {
                    listBox.SetSelected(i, true);
                }
            }
            listBox.EndUpdate();
            IsSelectingListBox = false;

            OnSelectedChanged(EventArgs.Empty);
        }

        public int GetSelectedIndex()
        {
            return listBox.SelectedIndex;
        }

        public int[] GetSelectedIndices()
        {
            int count = listBox.SelectedIndices.Count;
            int[] selectedIndices = new int[count];
            for (int i = 0; i < count; i++)
            {
                selectedIndices[i] = listBox.SelectedIndices[i];
            }
            return selectedIndices;
        }

        public string GetSelectedPath()
        {
            if (listBox.SelectedItem != null)
                return ((FileNameListItem)listBox.SelectedItem).FilePath;
            else
                return null;
        }

        public string[] GetSelectedPaths()
        {
            int count = listBox.SelectedItems.Count;
            string[] selectedPaths = new string[count];
            for (int i = 0; i < count; i++)
            {
                selectedPaths[i] = ((FileNameListItem)listBox.SelectedItems[i]).FilePath;
            }
            return selectedPaths;
        }

        public void SetSelectedIndex(int index)
        {
            SelectIndex(index, true);

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SetSelectedIndices(int[] indices)
        {
            SelectIndices(indices, true);

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SetSelectedPath(string path)
        {
            SelectPath(path, true);

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SetSelectedPaths(string[] paths)
        {
            SelectPaths(paths, true);

            OnSelectedChanged(EventArgs.Empty);
        }

        public string GetPathAt(int index)
        {
            if (index >= 0 && index < Count)
                return ((FileNameListItem)listBox.Items[index]).FilePath;
            else
                return null;
        }

        public int IndexOf(string path)
        {
            int index = -1;
            int itemCount = Count;
            for (int i = 0; i < itemCount; i++)
            {
                if (((FileNameListItem)listBox.Items[i]).FilePath.Equals(path))
                {
                    index = i;
                    break;
                }
            }
            return index;
        }

        public bool Contains(string path)
        {
            return _pathSet.Contains(path);
        }

        public void Clear()
        {
            _pathSet.Clear();
            listBox.Items.Clear();
            OnPathCleared(EventArgs.Empty);
        }

        public int Add(string path)
        {
            int theIndex = -1;
            if (path != null && !Contains(path))
            {
                if (listBox.Items.Add(new FileNameListItem(path)) != -1)
                {
                    theIndex = listBox.Items.Count - 1;
                    _pathSet.Add(path);
                    OnPathAdded(new PathEventArgs(path));
                }
            }
            return theIndex;
        }

        public void AddRange(IEnumerable<string> paths)
        {
            List<string> addedPathList = null;
            if (paths is ICollection<string>)
                addedPathList = new List<string>(((ICollection<string>)paths).Count);
            else
                addedPathList = new List<string>();

            listBox.BeginUpdate();
            foreach (string aPath in paths)
            {
                if (aPath != null && !Contains(aPath))
                {
                    listBox.Items.Add(new FileNameListItem(aPath));
                    _pathSet.Add(aPath);
                    addedPathList.Add(aPath);
                }
            }
            listBox.EndUpdate();
            OnPathsAdded(new PathsEventArgs(addedPathList.ToArray()));
        }

        public int Insert(int index, string path)
        {
            int theIndex = -1;
            if ((index >= 0 && index < Count) && (path != null && !Contains(path)))
            {
                listBox.Items.Insert(index, new FileNameListItem(path));
                theIndex = index;
                _pathSet.Add(path);
                OnPathInserted(new PathInsertedEventArgs(index, path));
            }
            return theIndex;
        }

        public int Remove(string path)
        {
            int theIndex = IndexOf(path);
            if (theIndex != -1)
            {
                listBox.Items.RemoveAt(theIndex);
                _pathSet.Remove(path);
                OnPathRemoved(new PathEventArgs(path));
            }
            return theIndex;
        }

        public int RemoveAt(int index)
        {
            int theIndex = -1;
            if (index >= 0 && index < Count)
            {
                string path = ((FileNameListItem)listBox.Items[index]).FilePath;
                listBox.Items.RemoveAt(index);
                theIndex = index;
                _pathSet.Remove(path);
                OnIndexRemoved(new IndexEventArgs(index));
            }
            return theIndex;
        }

        public int Rename(string srcPath, string dstPath)
        {
            int theIndex = -1;
            if (dstPath != null && !Contains(dstPath))
            {
                theIndex = IndexOf(srcPath);
                if (theIndex != -1)
                {
                    IsRenamingFile = true;
                    ((FileNameListItem)listBox.Items[theIndex]).FilePath = dstPath;
                    _pathSet.Remove(srcPath);
                    _pathSet.Add(dstPath);
                    IsRenamingFile = false;
                    OnRenamed(new RenamedEventArgs(srcPath, dstPath));
                }
            }
            return theIndex;
        }

        public void EnsureVisible(int index)
        {

        }

        public void EnsureVisible(string path)
        {

        }

        private void SelectIndex(int index, bool selected)
        {
            IsSelectingListBox = true;
            int itemCount = Count;
            if (index >= 0 && index < itemCount)
                listBox.SetSelected(index, selected);
            IsSelectingListBox = false;
        }

        private void SelectIndices(IEnumerable<int> indices, bool selected)
        {
            IsSelectingListBox = true;
            int itemCount = Count;
            foreach (int anIndex in indices)
            {
                if (anIndex >= 0 && anIndex < itemCount)
                    listBox.SetSelected(anIndex, selected);
            }
            IsSelectingListBox = false;
        }

        private void SelectPath(string path, bool selected)
        {
            if (String.IsNullOrEmpty(path))
                return;

            IsSelectingListBox = true;
            int itemCount = Count;
            for (int i = 0; i < itemCount; i++)
            {
                if (((FileNameListItem)listBox.Items[i]).FilePath.Equals(path))
                {
                    listBox.SetSelected(i, selected);
                    break;
                }
            }
            IsSelectingListBox = false;
        }

        private void SelectPaths(IEnumerable<string> paths, bool selected)
        {
            IsSelectingListBox = true;
            List<string> pathList = new List<string>(paths);
            int itemCount = Count;
            string thePath = null;
            for (int i = 0; i < itemCount; i++)
            {
                thePath = ((FileNameListItem)listBox.Items[i]).FilePath;
                if (pathList.Contains(thePath))
                {
                    listBox.SetSelected(i, selected);
                    pathList.Remove(thePath);
                    if (pathList.Count == 0)
                        break;
                }
            }
            IsSelectingListBox = false;
        }

        #endregion

        #region Event Trigger Methods

        protected void OnSelectedChanged(EventArgs e)
        {
            if (SelectedChanged != null)
                SelectedChanged(this, e);
        }

        protected void OnPathCleared(EventArgs e)
        {
            if (PathCleared != null)
                PathCleared(this, e);
        }

        protected void OnPathAdded(PathEventArgs e)
        {
            if (PathAdded != null)
                PathAdded(this, e);
        }

        protected void OnPathsAdded(PathsEventArgs e)
        {
            if (PathsAdded != null)
                PathsAdded(this, e);
        }

        protected void OnPathInserted(PathInsertedEventArgs e)
        {
            if (PathInserted != null)
                PathInserted(this, e);
        }

        protected void OnPathRemoved(PathEventArgs e)
        {
            if (PathRemoved != null)
                PathRemoved(this, e);
        }

        protected void OnIndexRemoved(IndexEventArgs e)
        {
            if (IndexRemoved != null)
                IndexRemoved(this, e);
        }

        protected void OnRenamed(RenamedEventArgs e)
        {
            if (Renamed != null)
                Renamed(this, e);
        }

        #endregion

        #region listBox Event Handler Methods

        private void listBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (IsRenamingFile || IsSelectingListBox)
                return;

            OnSelectedChanged(new IndicesEventArgs(SelectedIndices));
        }

        #endregion

        #region FileList Properties

        [Browsable(false)]
        public int Count
        {
            get 
            {
                return listBox.Items.Count;
            }
        }

        [Browsable(false)]
        public string[] Paths
        {
            get
            {
                int itemCount = Count;
                string[] paths = new string[itemCount];
                for (int i = 0; i < itemCount; i++)
                {
                    paths[i] = ((FileNameListItem)listBox.Items[i]).FilePath;
                }
                return paths;
            }
        }

        [Browsable(false)]
        [DefaultValue(-1)]
        public int SelectedIndex
        {
            get
            {
                return GetSelectedIndex();
            }
            set
            {
                int[] selectedIndices = GetSelectedIndices();
                SelectIndices(selectedIndices, false);
                SelectIndex(value, true);
                OnSelectedChanged(EventArgs.Empty);
            }
        }

        [Browsable(false)]
        [DefaultValue(new int[0])]
        public int[] SelectedIndices
        {
            get
            {
                return GetSelectedIndices();
            }
            set
            {
                int[] selectedIndices = GetSelectedIndices();
                if (value == null)
                {
                    SelectIndices(selectedIndices, false);
                }
                else
                {
                    SelectIndices(selectedIndices.Except(value), false);
                    SelectIndices(value.Except(selectedIndices), true);
                }
                OnSelectedChanged(EventArgs.Empty);
            }
        }

        [Browsable(false)]
        [DefaultValue(null)]
        public string SelectedPath
        {
            get
            {
                return GetSelectedPath();
            }
            set
            {
                string[] selectedPaths = GetSelectedPaths();
                SelectPaths(selectedPaths, false);
                SelectPath(value, true);
                OnSelectedChanged(EventArgs.Empty);
            }
        }

        [Browsable(false)]
        [DefaultValue(new string[0])]
        public string[] SelectedPaths
        {
            get
            {
                return GetSelectedPaths();
            }
            set
            {
                string[] selectedPaths = GetSelectedPaths();
                if (value == null)
                {
                    SelectPaths(selectedPaths, false);
                }
                else
                {
                    SelectPaths(selectedPaths.Except(value), false);
                    SelectPaths(value.Except(selectedPaths), true);
                }
                OnSelectedChanged(EventArgs.Empty);
            }
        }

        public string this[int index]
        {
            get
            {
                return GetPathAt(index);
            }
        }

        /// <summary>
        /// Prepare to rename a file. When the theItem in fileListBox is renamed, event 'SelectedIndexChanged' should do nothing.
        /// </summary>
        private bool IsRenamingFile
        {
            get;
            set;
        }

        /// <summary>
        /// The items of fileListBox are being selected by some method, and the event 'SelectedIndexChanged' should do noting.
        /// </summary>
        private bool IsSelectingListBox
        {
            get;
            set;
        }

        #endregion

        #region FileList Events

        [Category("File List")]
        public event EventHandler SelectedChanged;

        [Category("File List")]
        public event EventHandler PathCleared;

        [Category("File List")]
        public event PathEventHandler PathAdded;

        [Category("File List")]
        public event PathsEventHandler PathsAdded;

        [Category("File List")]
        public event PathInsertedEventHandler PathInserted;

        [Category("File List")]
        public event PathEventHandler PathRemoved;

        [Category("File List")]
        public event IndexEventHandler IndexRemoved;

        [Category("File List")]
        public event RenamedEventHandler Renamed;

        #endregion
    }
}

// Only one selected.
// Selected path is influenced.